#include <detect.h>
#include <android_utils.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/system_properties.h>

#define JNIREG_CLASS "com/security/utils/SecurityUtils"
size_t IS_REPEAT = 0;
/**
 * 记录主线程id
 */
pid_t MAIN_THREAD_ID;

JavaVM *mVm = NULL;

// JAVA本地接口方法表
static const JNINativeMethod METHODS[] = {
        {"check", "(Landroid/content/Context;)V", (void *) check}
#ifdef DEBUG
        /*,
        {"test", "(Ljava/lang/String;)[B", (void *) test}*/
#endif
};

/*
* Register several native methods for one class.
*/

static int registerNativeMethods(JNIEnv *env, const char *className,
                                 JNINativeMethod *gMethods, int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

static int registerNatives(JNIEnv *env) {
    if (!registerNativeMethods(env, JNIREG_CLASS, (JNINativeMethod *) (METHODS),
                               sizeof(METHODS) / sizeof(METHODS[0])))
        return JNI_FALSE;

    return JNI_TRUE;
}

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    jint result = -1;
    if (NULL != vm) {
        mVm = vm;
    } else {
        return result;
    }

    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
//    assert(env != NULL);

    if (!registerNatives(env)) {//注册
        return -1;
    }

/* success -- return valid version number */

    result = JNI_VERSION_1_4;

    return result;
}

void check(JNIEnv *jniEnv, jobject thiz, jobject context) {

    MAIN_THREAD_ID = gettid();

    LOGI("主线程id 为 %d", MAIN_THREAD_ID);

    if (IS_REPEAT) {
        return;
    }
    IS_REPEAT = 1;
    if (NULL == context) {
        jclass IllegalArgumentException_clazz = (*jniEnv)->FindClass(jniEnv,
                                                                     "java/lang/IllegalArgumentException");
        checkExc(jniEnv);
        (*jniEnv)->ThrowNew(jniEnv, IllegalArgumentException_clazz, "context 不能为null");
        (*jniEnv)->DeleteLocalRef(jniEnv, IllegalArgumentException_clazz);
        return;
    }
    cacheClazz(jniEnv, thiz, context);

    if (isDebuggerConnected(jniEnv)) {
        LOGI("检测到 debugger 模式打开");
#ifndef DEBUG
        reportRemote(jniEnv, KILL_APP_DEBUGGER);
        killApp();
        return;
#endif
    }

    //检测线程
    detectThread();
}


void freeRef(JNIEnv *jniEnv) {
    for (int i = 0; i < LEN; ++i) {
        pEntity entity = gRefMap[i];
        if (NULL != entity) {
            (*jniEnv)->DeleteGlobalRef(jniEnv, entity->value);
        }
    }
//    for (int j = 0; j < (LEN - 1); ++j) {
//        jobject obj = lRefList[j];
//        if (NULL != obj) {
//            (*jniEnv)->DeleteLocalRef(jniEnv, obj);
//        }
//    }
    gRefMapClear();
    lRefListClear();
}

void killAppCallBack() {

    sleep(20);
    LOGI("app is killed by security");
    //复位检测标记
    IS_REPEAT = 0;
    kill(getpid(), SIGKILL);
}

void killApp() {

    pid_t currentTid = gettid();
    if (currentTid == MAIN_THREAD_ID) {
        LOGI("******* main thread ******");
        //主线程 启动一个子线程防止 延迟杀死app 出现ANR问题
        pthread_t t;
        if (pthread_create(&t, NULL, killAppCallBack, (void *) NULL) != 0) {
            exit(-1);
        };
        pthread_detach(t);
    } else {
        //非主线程
        LOGI("******* not main thread ******");
        killAppCallBack();
    }
}

int checkExc(JNIEnv *env) {
    if ((*env)->ExceptionOccurred(env)) {
        (*env)->ExceptionDescribe(env);//写入到日志
        (*env)->ExceptionClear(env);
        return 1;
    }
    return 0;
}

int checkSystemDebuggerAble() {

    char PROP[PROP_VALUE_MAX];
    __system_property_get("ro.debuggable", PROP);
    int debuggable = atoi(PROP);
    LOGI("the value of ro.debuggable is %d", debuggable);

    if (debuggable != 0) {
        return 1;
    }
    return 0;
}

int isDebuggerConnected(JNIEnv *env) {
    jobject context = gRefMapGet("Context");
    if (context != NULL) {
        jclass context_clazz = (jclass) gRefMapGet("ContextClazz");
        jmethodID Context_clazz_getApplicationInfo_id = NULL;
        jobject ApplicationInfo_obj = NULL;
        jfieldID ApplicationInfo_flags_id = NULL;
        if (NULL != context_clazz) {

            Context_clazz_getApplicationInfo_id = (*env)->GetMethodID(env, context_clazz,
                                                                      "getApplicationInfo",
                                                                      "()Landroid/content/pm/ApplicationInfo;");
        }

        if (NULL != context && NULL != Context_clazz_getApplicationInfo_id) {

            ApplicationInfo_obj = (*env)->CallObjectMethod(env, context,
                                                           Context_clazz_getApplicationInfo_id);
        }

        jclass ApplicationInfo_clazz = (jclass) gRefMapGet("ApplicationInfo");
        if (NULL != ApplicationInfo_clazz) {
            ApplicationInfo_flags_id = (*env)->GetFieldID(env, ApplicationInfo_clazz, "flags", "I");
        }
        if (NULL != ApplicationInfo_obj && NULL != ApplicationInfo_flags_id) {
            jint flags = (*env)->GetIntField(env, ApplicationInfo_obj, ApplicationInfo_flags_id);
            if ((flags &= 2) != 0) {
                return 1;
            }
        }
    }

    return 0;
}

void cacheClazz(JNIEnv *jniEnv, jobject thiz, jobject pJobject) {

    int localIndex = 0, globalIndex = 0;
    if (pJobject != NULL) {
        pEntity ptr = (pEntity) malloc(sizeof(Entity));
        ptr->key = "Context";
        ptr->value = (*jniEnv)->NewGlobalRef(jniEnv, pJobject);
        gRefMapAdd("Context", (*jniEnv)->NewGlobalRef(jniEnv, pJobject), globalIndex++);
    }


    jclass g_security_utils_clazz = (*jniEnv)->FindClass(jniEnv, JNIREG_CLASS);
    checkExc(jniEnv);
    addLocalAndGlobalRef(jniEnv, "SecurityUtilsClazz", g_security_utils_clazz, localIndex++,
                         globalIndex++);
    jclass g_context_clazz = (*jniEnv)->FindClass(jniEnv, "android/content/Context");
    checkExc(jniEnv);
    addLocalAndGlobalRef(jniEnv, "ContextClazz", g_context_clazz, localIndex++, globalIndex++);

    jobject g_package_manager_clazz = (*jniEnv)->FindClass(jniEnv,
                                                           "android/content/pm/PackageManager");
    checkExc(jniEnv);
    addLocalAndGlobalRef(jniEnv, "PackageManager", g_package_manager_clazz, localIndex++,
                         globalIndex++);

    jobject Class_clazz = (*jniEnv)->FindClass(jniEnv, "java/lang/Class");
    checkExc(jniEnv);
    addLocalAndGlobalRef(jniEnv, "Class", Class_clazz, localIndex++, globalIndex++);

    jobject Object_clazz = (*jniEnv)->FindClass(jniEnv, "java/lang/Object");
    checkExc(jniEnv);
    addLocalAndGlobalRef(jniEnv, "Object", Object_clazz, localIndex++, globalIndex++);

    jobject Signature_clazz = (*jniEnv)->FindClass(jniEnv, "android/content/pm/Signature");
    checkExc(jniEnv);
    addLocalAndGlobalRef(jniEnv, "Signature", Signature_clazz, localIndex++, globalIndex++);

    jobject PackageInfo_clazz = (*jniEnv)->FindClass(jniEnv, "android/content/pm/PackageInfo");
    checkExc(jniEnv);
    addLocalAndGlobalRef(jniEnv, "PackageInfo", PackageInfo_clazz, localIndex++, globalIndex++);

    jobject Exception_clazz = (*jniEnv)->FindClass(jniEnv, "java/lang/Exception");
    checkExc(jniEnv);
    addLocalAndGlobalRef(jniEnv, "Exception", Exception_clazz, localIndex++, globalIndex++);

    jobject List_clazz = (*jniEnv)->FindClass(jniEnv, "java/util/List");
    checkExc(jniEnv);
    addLocalAndGlobalRef(jniEnv, "List", List_clazz, localIndex++, globalIndex++);

    jobject Throwable_clazz = (*jniEnv)->FindClass(jniEnv, "java/lang/Throwable");
    checkExc(jniEnv);
    addLocalAndGlobalRef(jniEnv, "Throwable", Throwable_clazz, localIndex++, globalIndex++);

    jobject Modifier_clazz = (*jniEnv)->FindClass(jniEnv, "java/lang/reflect/Modifier");
    checkExc(jniEnv);
    addLocalAndGlobalRef(jniEnv, "Modifier", Modifier_clazz, localIndex++, globalIndex++);

    jobject ApplicationInfo_clazz = (*jniEnv)->FindClass(jniEnv,
                                                         "android/content/pm/ApplicationInfo");
    checkExc(jniEnv);
    addLocalAndGlobalRef(jniEnv, "ApplicationInfo", ApplicationInfo_clazz, localIndex++,
                         globalIndex++);

}

void addLocalAndGlobalRef(JNIEnv *jniEnv, const char *name, jobject obj, int localIndex,
                          int globalIndex) {
    if (NULL != obj) {

        lRefList[localIndex] = obj;
        gRefMapAdd(name, (*jniEnv)->NewGlobalRef(jniEnv, obj), globalIndex);
    }
}

void gRefMapAdd(const char *key, jobject value, int index) {

    pEntity ptr = (pEntity) malloc(sizeof(Entity));
    ptr->key = key;
    ptr->value = value;
    gRefMap[index] = ptr;
}

jobject gRefMapGet(const char *key) {
    for (int i = 0; i < LEN; i++) {
        pEntity entity = gRefMap[i];
        if (strcmp(entity->key, key) == 0) {
            return entity->value;
        }
    }
    return NULL;
}

void gRefMapClear() {
    for (int i = 0; i < LEN; i++) {
        pEntity entity = gRefMap[i];
        free(entity);
    }
    LOGI("free global ref end!");
}

void lRefListClear() {
    for (int i = 0; i < (LEN - 1); i++) {
        lRefList[i] = NULL;
    }
    LOGI("free local ref end!");
}

int detectionFridaPort() {
    struct sockaddr_in sa;
    memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;
    inet_aton("127.0.0.1", &(sa.sin_addr));
    int sock;
    char res[7];
    for (unsigned int i = 0; i <= 65535; i++) {
        sock = socket(AF_INET, SOCK_STREAM, 0);
        sa.sin_port = htons(i);
        int ret = connect(sock, (struct sockaddr *) &sa, sizeof(sa));
        if (ret != -1) {
            memset(res, 0, 7);

            send(sock, "\x00", 1, NULL);
            send(sock, "AUTH\r\n", 6, NULL);
            usleep(100);
            ret = recv(sock, res, 6, MSG_DONTWAIT);
            if (ret != -1) {
                if (strcmp(res, "REJECT") == 0) {
                    LOGI("FRIDA DETECTED [1] - frida server running on port %d!", i);
                    return 1;
                }
            } else {
                LOGI(" 11 ret is %d", ret);
            }

        } else {
//            LOGI(" 00 %d ret is %d", i, ret);
        }
        close(sock);
    }

    return 0;
}

int checkIdaServer() {


    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd != -1) {

        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(23946);
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
        inet_aton("127.0.0.1", &(addr.sin_addr));
        int ret = connect(fd, (struct sockaddr *) &addr, sizeof(addr));
        if (ret != -1) {
            return 1;
        }

    } else {
        LOGI(" checkIdaServer socket fd is %d", fd);
    }
    return 0;
}

int checkPidStatus() {

    FILE *fd = fopen("/proc/self/status", "r");
    char line[BUFFER_LEN];
    while (fgets(line, BUFFER_LEN, fd)) {

        LOGI("%s", line);
        if (strstr(line, "TracerPid:")) {
            int tracerPid;
            sscanf(line, "TracerPid:     %d", &tracerPid);
            if (tracerPid != 0) {
                return 1;
            }
        }
    }
    fclose(fd);

    return 0;
}


static void *check_loop(void *arg) {

    JNIEnv *env = NULL;
    bool isAttacked;

    if (NULL != mVm) {
        if ((*mVm)->AttachCurrentThread(mVm, &env, NULL) != JNI_OK) {
            return NULL;
        } else {
            isAttacked = true;
        }
    }

    if (isAttacked) {

        char path[256];
        char addr[256];
        char perm[5];
        char offset_str[256];
        char line[BUFFER_LEN];
        int loop = 0;

        while (loop < 3) {

            LOGI("loop %d", loop);

            if (checkSystemDebuggerAble()) {

                LOGI("检测到 System debuggable 为可调试");
#ifndef DEBUG
                reportRemote(env, KILL_APP_SYSTEM_DEBUGGABLE);
                (*mVm)->DetachCurrentThread(mVm);
                killApp();
                return NULL;
#endif
            }

            if (checkIdaServer()) {
                LOGI("检测到 IDA PRO 的监听的端口");
#ifndef DEBUG
                reportRemote(env, KILL_APP_IDA);
                (*mVm)->DetachCurrentThread(mVm);
                killApp();
                return NULL;
#endif
            }

//检测进程状态
            if (checkPidStatus()) {
                LOGI("检测到 当前进程状态有异常");
#ifndef DEBUG
                reportRemote(env, KILL_APP_TRACE_PID);
                (*mVm)->DetachCurrentThread(mVm);
                killApp();
                return NULL;
#endif
            }

            if (detectionFridaPort()) {
                LOGI("检测到 Frida 端口");
#ifndef DEBUG
                reportRemote(env, KILL_APP_FRIDA_PORT);
                (*mVm)->DetachCurrentThread(mVm);
                killApp();
                return NULL;
#endif
            }

            FILE *fp;
            fp = fopen("/proc/self/maps", "r");
            if (fp == NULL) {
                LOGI("open maps error");
                return NULL;
            } else {

                while (fgets(line, sizeof(line), fp)) {

                    if (sscanf(line, "%s %4s %s %*s %*s %s", addr, perm, offset_str, path) != 4) {
                        continue;
                    }

                    if (strcasestr(path, "frida")) {
                        LOGI(" 111 frida found in memory!");
                        fclose(fp);
#ifndef DEBUG
                        reportRemote(env, KILL_APP_FRIDA_MEMORY);
                        (*mVm)->DetachCurrentThread(mVm);
                        killApp();
                        return NULL;
#endif
                    }
                }
            }
            fclose(fp);
            loop++;
            sleep(30);
        }
        freeRef(env);
        (*mVm)->DetachCurrentThread(mVm);
        IS_REPEAT = 0;
    }
    return NULL;
}

void detectThread() {
    pthread_t t;
    if (pthread_create(&t, NULL, check_loop, (void *) NULL) != 0) {
        exit(-1);
    };
    pthread_detach(t);
}

void reportRemote(JNIEnv *jniEnv, size_t type) {

    static jmethodID reportRemote_id, getInstance_id;
    jclass YouluUtils_clazz = gRefMapGet("SecurityUtilsClazz");
    if (NULL != YouluUtils_clazz) {
        if (NULL == reportRemote_id) {
            reportRemote_id = (*jniEnv)->GetMethodID(jniEnv, YouluUtils_clazz, "reportRemote",
                                                     "(I)V");
            checkExc(jniEnv);
        }
        if (NULL == getInstance_id) {
            getInstance_id = (*jniEnv)->GetStaticMethodID(jniEnv, YouluUtils_clazz, "getInstance",
                                                          "()Lcom/security/utils/SecurityUtils;");
            checkExc(jniEnv);
        }

//    jstring str = char2jstring(jniEnv, type);
        jobject instance = (*jniEnv)->CallStaticObjectMethod(jniEnv, YouluUtils_clazz,
                                                             getInstance_id);
        (*jniEnv)->CallVoidMethod(jniEnv, instance, reportRemote_id, (jint) type);
    } else {
        LOGE("SecurityUtilsClazz is null");
    }
}